package cn.snowheart.singleton;

/**
 * 懒汉模式 - 第四版
 * Note: 优化了 getInstance() 方法，似乎解决了 SingletonV3.java 中提到的问题，将synchronized关键字加在了内部，
 * 也就是说当调用的时候是不需要加锁的，只有在instance为null，并创建对象的时候才需要加锁，性能有一定的提升。
 * <p>
 * 但是，这样的情况，还是有可能有问题的，看下面的情况：
 * 在Java指令中创建对象和赋值操作是分开进行的，也就是说instance = new Singleton();语句是分两步执行的。
 * 但是JVM并不保证这两个操作的先后顺序，也就是说有可能JVM会为新的Singleton实例分配空间，然后直接赋值给instance成员，
 * 然后再去初始化这个Singleton实例。这样就可能出错了。
 * <p>
 * 我们以A、B两个线程为例：
 * 【1】：A、B线程同时进入了第一个if判断
 * 【2】：A首先进入synchronized块，由于instance为null，所以它执行instance = new Singleton();
 * 【3】：由于JVM内部的优化机制，JVM先画出了一些分配给Singleton实例的空白内存，
 * 并赋值给instance成员（注意此时JVM没有开始初始化这个实例），然后A离开了synchronized块。
 * 【4】：B进入synchronized块，由于instance此时不是null，因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
 * 【5】：此时B线程打算使用Singleton实例，却发现它没有被初始化，于是错误发生了。NullPointerException
 * <p>
 * 所以，程序还是有可能发生错误，其实程序在运行过程是很复杂的，从这点我们就可以看出，尤其是在写多线程环境下的程序更有难度，有挑战性。
 *
 * @author liuwanxiang
 * @version 2019/07/02
 */
public class Singleton {

    private static Singleton instance;

    private int value;

    private Singleton() {
        value = 0;
    }

    public int increase() {
        return value++;
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (instance) {
                if (instance == null) {
                    try {
                        // 线程sleep一段时间，再进行初始化操作
                        // 模拟实际过程中的一个实例加载所需要的时间
                        long times = (long) (Math.random() * 1000);
                        Thread.sleep(times);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}
